import Konva from "konva";
import { Draw } from "./Draw";
import { IBackgroundLayerOptions } from "../Interface/Pptx";
import { getColorMatch, getUniqueId, nextTick } from "../Utils";

/**
 * 图层管理器 - layerList ：Konva.Layer[]
 *  1. 添加图层
 *    1.1 新建图层后 addSlide
 *    1.2 更新元素后 render
 *    1.3 新建另一个图层前 addSlide
 *
 *  2. 通过绑定唯一的 layer ID 识别图层
 *    2.1 如果添加已经存在的图层，则更新图层
 *    2.2 如果添加不存在的图层，则添加图层
 *
 *  3. 切换至指定图层
 *    3.1 先清空所有图层
 *    3.2 将指定图层添加至 stage
 *    3.3 重新渲染 stage（重新渲染会重新更新图层）
 */

export class LayerManager {
  private draw: Draw;
  private layerList: Konva.Layer[] = [];
  private gridLine: boolean = false;
  private currentIndex = -1;

  /** 图层管理器还需要兼职复制粘贴图形、幻灯片的能力 */
  private copyGroupList: Konva.Group[] = [];
  private copyLayerList: Konva.Layer | null = null;

  constructor(draw: Draw) {
    this.draw = draw;
  }

  /**
   * 缓存图层 - 对完提供的方法，这里只接受 layer，至于是更新图层还是添加新图层，通过 ID 判断实现
   * @returns
   */
  public cacheLayer() {
    const layer = this.draw.getLayer();
    if (!layer) return;

    // 如果存在幻灯片 则更新
    const haveLayer = this.layerList.find((ly) => ly.id() === layer.id());
    if (haveLayer) return this.updateLayer(layer.clone());

    /**
     * 不然，新的图层永远添加到激活的幻灯片后面
     *  考虑到协同的场景，需要确保用户的幻灯片顺序一致性，因此，这个 currentIndex 不能直接++
     */
    this.layerList.splice(this.currentIndex + 1, 0, layer.clone());
    this.currentIndex++;
  }

  /**
   * Command API : executePrevSlide - 上一个图层
   */
  public prevSlide() {
    const layer = this.draw.getLayer(); // 1. 获取当前图层
    if (!layer) return;
    const index = this.getLayerIndex(layer); // 2. 解析当前 index
    if (index === 0) return;
    this.switchLayer(index - 1); // 3. 切换到上一个图层
  }

  /**
   * Command API : executeNextSlide - 下一个图层
   */
  public nextSlide() {
    const layer = this.draw.getLayer(); // 1. 获取当前图层
    if (!layer) return;
    const index = this.getLayerIndex(layer); // 2. 解析当前 index
    if (index === this.layerList.length - 1) return;
    this.switchLayer(index + 1); // 3. 切换到下一个图层
  }

  /**
   * 切换至指定图层 ： cacheLayer  先保存当前图层，不然动画会有问题
   * @param index
   * @returns
   */
  public switchLayer(index: number) {
    if (isNaN(index)) this.currentIndex = -1;
    else this.currentIndex = index;

    if (index > this.layerList.length) return;

    this.cacheLayer();

    const layer = this.layerList[index];

    const stage = this.draw.getStage();

    if (!layer || !stage) return;

    this.draw.clearLayer(); // 清空所有layer

    stage.add(layer); // 将被切换的图层添加到容器内

    this.draw.setLayer(layer); // 重新修正layer指向

    // 切换幻灯片重新渲染背景-防止用户在另一个幻灯片打开网格，这幻灯片没有更新
    this.setBackgroundLayer();

    this.draw.render({ cacheLayer: false }); // 重新渲染

    this.draw.getSlideAnimation().applyAnimation(); // 应用动画
  }

  /**
   * 移除图层 - 删除幻灯片/图层方法
   * @param index
   */
  public deleteSlide(index: number) {
    // 删除图层
    this.layerList.splice(index, 1);

    /**
     * 处理激活问题
     *  1. 如果删除的index > currentIndex 则不需要处理
     *  2. 如果删除的 index < currentIndex  currentIndex--
     *  3. 如果删除的 index = currentIndex 并且 === 0 判断是否还存在幻灯片，存在则切换下一张
     */
    if (index <= this.currentIndex) this.currentIndex--;

    if (index === 0 && this.layerList.length) {
      // 切换至下一张幻灯片
      this.currentIndex++;

      const layer = this.layerList[0];

      const stage = this.draw.getStage();

      if (!layer || !stage) return;

      this.draw.clearLayer(); // 清空所有layer

      stage.add(layer); // 将被切换的图层添加到容器内

      this.draw.setLayer(layer); // 重新修正layer指向
    }

    // 处理默认值问题
    if (this.layerList.length === 0) {
      this.currentIndex = -1;
      this.draw.clearLayer();
      this.draw.setLayer(null);
      this.draw.createDefaultSlide();
    }

    this.draw.render({ cacheLayer: false });
  }

  /**
   * 根据某个顺序重新排序图层 - 用于 缩略图 拖拽排序
   */
  public updateLayerList(order: number[]) {
    this.layerList = order.map((index) => this.layerList[index]);
    this.draw.render(); // 重新渲染
  }

  /**
   * 获取所有图层的缩略图
   * @returns string[]
   */
  public getLayerImage() {
    const stage = this.draw.getStage();
    const { width, height } = stage.size();

    // 指定范围内的可视化
    const dataArr = this.layerList.map((layer) => {
      // 需要将网格线去除
      const newLayer = layer.clone();
      const bgGroup = <Konva.Group>newLayer.findOne(".bgGroup");
      bgGroup?.findOne(".bgLineGroup")?.destroy();
      // 将动画标签去掉
      newLayer.find(".konva-animation-label").forEach((label) => {
        label.destroy();
      });
      // 将线段控制点取消
      newLayer.find(".line-control").forEach((control) => control.destroy());

      return newLayer.toDataURL({ x: 0, y: 0, width, height });
    });

    const eventbus = this.draw.getEventBus();

    nextTick(() => {
      if (eventbus.isSubscribe("thumbChanged"))
        eventbus.emit("thumbChanged", dataArr);
    });
    return dataArr;
  }

  // 更新图层
  private updateLayer(newLayer: Konva.Layer) {
    const index = this.getLayerIndex(newLayer);
    this.layerList[index] = newLayer;
  }

  /** 获取给定 layer 在数组中的下标 */
  private getLayerIndex(layer: Konva.Layer) {
    return this.layerList.findIndex((ly) => ly.id() === layer.id());
  }

  /**
   * 获取当前 layer List 的 JSON 数据
   */
  public getLayerData() {
    return this.layerList.map((layer) => {
      const cloneLayer = layer.clone();
      // 删除 Transformer 节点
      cloneLayer.find("Transformer").forEach((tr) => tr.destroy());
      return cloneLayer.toJSON();
    });
  }

  /**
   * 为了实现页面数据保存、数据写入，需要支持数据的导入导出，目前使用 toJson 实现
   *  1. Rect 直接创建 Konva.Rect
   *  2. Text 直接创建 Konva.Text
   *  3. Star 直接创建 Konva.Star
   *  4. Ring 直接创建 konva.Ring
   *  5. Circle 直接创建 konva.Circle
   *  6. Ellipse 直接创建 konva.Ellipse
   *  7. Image 创建 Image 需要 source 字段，在创建完成时，保存该字段，值类型为 base64，使用服务器的场景下，推荐该字段写入服务器图片地址，避免前端存储字符串过大
   *  8. Video 实现类似，创建完成后，记录 source 字段，值为 data:video/mp4;base64,
   *  9. Table 自身有 data 属性，直接创建 Shape 即可
   *  10. Echart 自身有 数据，直接创建 Shape 即可
   *  11. Arrow 标记类型 insertShape 动态创建 Shape
   *  12. RichText 富文本本身是图片资源
   *  13. Shape 类型可能包含：Logo、Table、Echart、Arrow
   *  14. 动画节点标记还未处理哈
   *
   * ** 注意事项：
   *  1. 删除 Transformer 节点
   *
   */
  public parseLayerJson(str: string) {
    if (!str) return null;

    const konvaGraph = this.draw.getKonvaGraph();

    const layer = new Konva.Layer({ id: getUniqueId() });

    const grouplist = <Konva.Group[]>JSON.parse(str).children;

    grouplist.forEach((g) => layer.add(konvaGraph.createKonvaShape(g)));

    return layer;
  }

  /**
   * 背景图层管理
   */
  public setBackgroundLayer(payload?: IBackgroundLayerOptions) {
    const stage = this.draw.getStage();
    const layer = this.draw.getLayer();
    if (!layer) return;

    let fill =
      payload?.fill || <string>getColorMatch(this.draw, "slideFillColor");
    let stroke =
      payload?.stroke || <string>getColorMatch(this.draw, "slideStrokeColor");
    let gridColor = payload?.gridColor || "#eee";
    let source = payload?.image || "";
    let bgType = payload?.bgType || "fill";
    // 无法直接识别是应用图片背景还是纯色背景，因此，需要传递类型参数

    const oldBackGroundLayer = layer.findOne(".bgGroup");

    if (oldBackGroundLayer) {
      // 取旧的属性，设置为新的属性，不然切换会丢失
      fill = payload?.fill || oldBackGroundLayer.attrs.fill;
      stroke = payload?.stroke || oldBackGroundLayer.attrs.stroke;
      gridColor = payload?.gridColor || oldBackGroundLayer.attrs.gridColor;
      source = payload?.image || oldBackGroundLayer.attrs.source;
      bgType = payload?.bgType || oldBackGroundLayer.attrs.bgType;
      oldBackGroundLayer.destroy();
    }

    const { width, height } = stage.size();
    const group = new Konva.Group({
      x: 0,
      y: 0,
      width,
      height,
      name: "bgGroup",
      id: getUniqueId(),
    });

    // 如果是图片背景，则创建图片资源
    if (source && bgType === "image") {
      const image = new Image();
      image.src = source;
      group.setAttrs({ source });
      const bgImage = new Konva.Image({ x: 0, y: 0, width, height, image });
      layer.add(group.add(bgImage));
      nextTick(() => this.draw.render());
    } else {
      const rect = new Konva.Rect({ x: 0, y: 0, width, height, fill, stroke });
      layer.add(group.add(rect));
    }

    // 网格线
    group.setAttrs({ fill, gridColor, bgType });

    if (this.gridLine) this.drawGridLine(group);

    group.moveToBottom(); // 背景图层置底

    if (payload && payload.global) {
      // 如果是全局的话，直接循环赋值
      this.layerList.forEach((layer) => {
        // 避免引用问题
        const currentBgGroup = group.clone();
        // 先删除旧的
        layer.findOne(".bgGroup")?.destroy();
        layer.add(currentBgGroup);
        currentBgGroup.moveToBottom(); // 背景图层置底
      });
    }

    this.draw.render();
  }

  // 通过 layerID 获取指定的layer 图层
  public getLayerByID(layerID: string) {
    return this.layerList.find((layer) => layer.id() === layerID);
  }

  /** 网格线 */
  private drawGridLine(group: Konva.Group) {
    const { width, height, gridColor: stroke } = group.attrs;
    const dash = [5, 5];
    const lineGroup = new Konva.Group({
      name: "bgLineGroup",
      id: getUniqueId(),
    });

    const WIDTH_LINE_NUM = 16 * 1.5;
    const HEIGHT_LINE_NUM = 9 * 1.5;

    // 宽度分为 16条线
    for (let i = 0; i < width; i += width / WIDTH_LINE_NUM) {
      const line = new Konva.Line({ points: [i, 0, i, height], dash, stroke });
      lineGroup.add(line);
    }

    // 高度分为 9 条线
    for (let i = 0; i < height; i += height / HEIGHT_LINE_NUM) {
      const line = new Konva.Line({ points: [0, i, width, i], dash, stroke });
      lineGroup.add(line);
    }

    group.add(lineGroup);
  }

  public setCurrentIndex(index: number) {
    this.currentIndex = index;
  }

  public getGridLine = () => this.gridLine;
  public getLayerList = () => this.layerList;
  public getCurrentIndex = () => this.currentIndex;
  public getCopyGroupList = () => this.copyGroupList;
  public getCopyLayerList = () => this.copyLayerList;
  public clearLayerList = () => (this.layerList = []);
  public setGridLine = (payload: boolean) => (this.gridLine = payload);
  public setCopyGroupList = (list: Konva.Group[]) =>
    (this.copyGroupList = list);
  public setCopyLayerList = (layer: Konva.Layer) =>
    (this.copyLayerList = layer);
}
